<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.3.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/yuwanzi.io/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/yuwanzi.io/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/yuwanzi.io/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/yuwanzi.io/images/logo.svg" color="#222">

<link rel="stylesheet" href="/yuwanzi.io/css/main.css">



<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.1/css/all.min.css">
  <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/animate.css@3.1.1/animate.min.css">

<script class="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"suyuhuan.gitee.io","root":"/yuwanzi.io/","images":"/yuwanzi.io/images","scheme":"Muse","version":"8.2.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":false,"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"Suche...","empty":"We didn't find any results for the search: ${query}","hits_time":"${hits} results found in ${time} ms","hits":"${hits} results found"}};
  </script>
<meta name="description" content="在浏览器渲染过程与性能优化一文中（建议先去看一下这篇文章再来阅读本文），我们了解与认识了浏览器的关键渲染路径以及如何优化页面的加载速度。在本文中，我们主要关注的是如何提高浏览器的渲染性能（浏览器进行布局计算、绘制像素等操作）与效率。 很多网页都使用了看起来效果非常酷炫的动画与用户进行交互，这些动画效果显著提高了用户的体验，但如果因为性能原因导致动画的每秒帧数太低，反而会让用户体验变得更差（如果一个">
<meta property="og:type" content="article">
<meta property="og:title" content="浏览器性能优化-渲染性能">
<meta property="og:url" content="https://suyuhuan.gitee.io/yuwanzi.io/2017/10/08/2017-10-08-BrowserRenderOptimization/index.html">
<meta property="og:site_name" content="玉丸子 | Blog">
<meta property="og:description" content="在浏览器渲染过程与性能优化一文中（建议先去看一下这篇文章再来阅读本文），我们了解与认识了浏览器的关键渲染路径以及如何优化页面的加载速度。在本文中，我们主要关注的是如何提高浏览器的渲染性能（浏览器进行布局计算、绘制像素等操作）与效率。 很多网页都使用了看起来效果非常酷炫的动画与用户进行交互，这些动画效果显著提高了用户的体验，但如果因为性能原因导致动画的每秒帧数太低，反而会让用户体验变得更差（如果一个">
<meta property="og:locale">
<meta property="og:image" content="http://wx4.sinaimg.cn/large/63503acbly1fk9uu5zk8xj20o603zdh7.jpg">
<meta property="og:image" content="http://wx2.sinaimg.cn/large/63503acbly1fk9uu6h7edj20nr03wq46.jpg">
<meta property="og:image" content="http://wx4.sinaimg.cn/large/63503acbly1fk9uu6u2u3j20o5040wfm.jpg">
<meta property="og:image" content="http://wx4.sinaimg.cn/large/63503acbly1fka0wxn8m2j20p40by40d.jpg">
<meta property="og:image" content="http://wx3.sinaimg.cn/large/63503acbly1fka16g0tbyj20tq0lqacb.jpg">
<meta property="article:published_time" content="2017-10-08T04:00:00.000Z">
<meta property="article:modified_time" content="2020-11-07T00:58:17.000Z">
<meta property="article:author" content="玉丸子">
<meta property="article:tag" content="2017">
<meta property="article:tag" content="前端">
<meta property="article:tag" content="浏览器">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://wx4.sinaimg.cn/large/63503acbly1fk9uu5zk8xj20o603zdh7.jpg">


<link rel="canonical" href="https://suyuhuan.gitee.io/yuwanzi.io/2017/10/08/2017-10-08-BrowserRenderOptimization/">


<script class="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh'
  };
</script>
<title>浏览器性能优化-渲染性能 | 玉丸子 | Blog</title>
  




  <noscript>
  <style>
  body { margin-top: 2rem; }

  .use-motion .menu-item,
  .use-motion .sidebar,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header {
    visibility: visible;
  }

  .use-motion .header,
  .use-motion .site-brand-container .toggle,
  .use-motion .footer { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle,
  .use-motion .custom-logo-image {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line {
    transform: scaleX(1);
  }

  .search-pop-overlay, .sidebar-nav { display: none; }
  .sidebar-panel { display: block; }
  </style>
</noscript>

<link rel="alternate" href="/yuwanzi.io/atom.xml" title="玉丸子 | Blog" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="Navigationsleiste an/ausschalten" role="button">
    </div>
  </div>

  <div class="site-meta">

    <a href="/yuwanzi.io/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">玉丸子 | Blog</h1>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>







</div>
        
  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>

  <aside class="sidebar">

    <div class="sidebar-inner sidebar-nav-active sidebar-toc-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          Inhaltsverzeichnis
        </li>
        <li class="sidebar-nav-overview">
          Übersicht
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
            <div class="post-toc animated"><ol class="nav"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%83%8F%E7%B4%A0%E7%AE%A1%E9%81%93"><span class="nav-number">1.</span> <span class="nav-text">像素管道</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8RequestAnimationFrame%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E5%8A%A8%E7%94%BB"><span class="nav-number">2.</span> <span class="nav-text">使用RequestAnimationFrame函数实现动画</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Web-Workers"><span class="nav-number">3.</span> <span class="nav-text">Web Workers</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%99%8D%E4%BD%8E%E6%A0%B7%E5%BC%8F%E8%AE%A1%E7%AE%97%E7%9A%84%E5%A4%8D%E6%9D%82%E5%BA%A6"><span class="nav-number">4.</span> <span class="nav-text">降低样式计算的复杂度</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%81%BF%E5%85%8D%E5%BC%BA%E5%88%B6%E5%90%8C%E6%AD%A5%E5%B8%83%E5%B1%80%E5%92%8C%E5%B8%83%E5%B1%80%E6%8A%96%E5%8A%A8"><span class="nav-number">5.</span> <span class="nav-text">避免强制同步布局和布局抖动</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8%E4%B8%8D%E4%BC%9A%E8%A7%A6%E5%8F%91%E5%B8%83%E5%B1%80%E4%B8%8E%E7%BB%98%E5%88%B6%E7%9A%84%E5%B1%9E%E6%80%A7%E6%9D%A5%E5%AE%9E%E7%8E%B0%E5%8A%A8%E7%94%BB"><span class="nav-number">6.</span> <span class="nav-text">使用不会触发布局与绘制的属性来实现动画</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE"><span class="nav-number">7.</span> <span class="nav-text">参考文献</span></a></li></ol></div>
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">玉丸子</p>
  <div class="site-description" itemprop="description">这里是玉丸子的个人博客,与你一起发现更大的世界。</div>
</div>
<div class="site-state-wrap site-overview-item animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/yuwanzi.io/archives">
          <span class="site-state-item-count">68</span>
          <span class="site-state-item-name">Artikel</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/yuwanzi.io/categories/">
        <span class="site-state-item-count">39</span>
        <span class="site-state-item-name">Kategorien</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/yuwanzi.io/tags/">
        <span class="site-state-item-count">46</span>
        <span class="site-state-item-name">schlagwörter</span></a>
      </div>
  </nav>
</div>



        </div>
      </div>
    </div>
  </aside>
  <div class="sidebar-dimmer"></div>


    </header>

    
  <div class="back-to-top" role="button">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


    <div class="main-inner post posts-expand">


  


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh">
    <link itemprop="mainEntityOfPage" href="https://suyuhuan.gitee.io/yuwanzi.io/2017/10/08/2017-10-08-BrowserRenderOptimization/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/yuwanzi.io/images/avatar.gif">
      <meta itemprop="name" content="玉丸子">
      <meta itemprop="description" content="这里是玉丸子的个人博客,与你一起发现更大的世界。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="玉丸子 | Blog">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          浏览器性能优化-渲染性能
        </h1>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">Veröffentlicht am</span>

      <time title="Erstellt: 2017-10-08 12:00:00" itemprop="dateCreated datePublished" datetime="2017-10-08T12:00:00+08:00">2017-10-08</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">Bearbeitet am</span>
        <time title="Geändert am: 2020-11-07 08:58:17" itemprop="dateModified" datetime="2020-11-07T08:58:17+08:00">2020-11-07</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">in</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">前端</span></a>
        </span>
          . 
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/yuwanzi.io/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/" itemprop="url" rel="index"><span itemprop="name">浏览器</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
        <p>在<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/10/03/2017-10-03-BrowserCriticalRenderingPath/">浏览器渲染过程与性能优化</a>一文中（建议先去看一下这篇文章再来阅读本文），我们了解与认识了浏览器的关键渲染路径以及如何优化页面的加载速度。在本文中，我们主要关注的是如何提高浏览器的渲染性能（浏览器进行布局计算、绘制像素等操作）与效率。</p>
<p>很多网页都使用了看起来效果非常酷炫的动画与用户进行交互，这些动画效果显著提高了用户的体验，但如果因为性能原因导致动画的每秒帧数太低，反而会让用户体验变得更差（如果一个酷炫的动画效果运行起来总是经常卡顿或者看起来反应很慢，这些都会让用户感觉糟透了）。</p>
<p>一个流畅的动画需要保持在每秒60帧，换算成毫秒浏览器需要在10毫秒左右完成渲染任务（每秒有1000毫秒，1000/60 约等于 16毫秒一帧，但浏览器还有其他工作需要占用时间，所以估算为10毫秒），如果能够理解浏览器的渲染过程并发现性能瓶颈对其优化，可以使你的项目变得具有交互性且动画效果如飘柔般顺滑。</p>
<blockquote>
<p>本文作者为: <a target="_blank" rel="noopener" href="https://github.com/SylvanasSun">SylvanasSun(sylvanas.sun@gmail.com)</a>.转载请务必将本段话置于文章开头处(保留超链接).<br>本文首发自<a target="_blank" rel="noopener" href="https://sylvanassun.github.io/">SylvanasSun Blog</a>,原文链接: <a target="_blank" rel="noopener" href="https://sylvanassun.github.io/2017/10/08/2017-10-08-BrowserRenderOptimization/">https://sylvanassun.github.io/2017/10/08/2017-10-08-BrowserRenderOptimization/</a></p>
</blockquote>
<h3 id="像素管道"><a href="#像素管道" class="headerlink" title="像素管道"></a>像素管道</h3><hr>
<p><strong>所谓像素管道其实就是浏览器将渲染树绘制成像素的流程。管道的每个区域都有可能产生卡顿，即管道中的某一区域如果发生变化，浏览器将会进行自动重排，然后重新绘制受影响的区域。</strong></p>
<p><img src="http://wx4.sinaimg.cn/large/63503acbly1fk9uu5zk8xj20o603zdh7.jpg" alt="像素管道"></p>
<ul>
<li><p>JavaScript：<strong>该区域其实指的是实现动画效果的方法</strong>，一般使用<code>JavaScript</code>来实现动画，例如<code>JQuery</code>的<code>animate</code>函数、对一个数据集进行排序或动态添加一些<code>DOM</code>节点等。当然，也可以使用其他的方法来实现动画效果，像<code>CSS</code>的<code>Animation</code>、<code>Transition</code>和<code>Transform</code>。</p>
</li>
<li><p>Style：<strong>该区域为样式计算阶段，浏览器会根据选择器（就是<code>CSS</code>选择器，如<code>.td</code>）计算出哪些节点应用哪些<code>CSS</code>规则，然后计算出每个节点的最终样式并应用到节点上。</strong></p>
</li>
<li><p>Layout：<strong>该区域为布局计算阶段，浏览器会在该过程中根据节点的样式规则来计算它要占据的空间大小以及在屏幕中的位置</strong>。</p>
</li>
<li><p>Paint：<strong>该区域为绘制阶段，浏览器会先创建绘图调用的列表，然后填充像素</strong>。绘制阶段会涉及到文本、颜色、图像、边框和阴影，基本上包括了每个可视部分。绘制一般是在多个图层（用过<code>Photoshop</code>等图片编辑软件的童鞋一定很眼熟图层这个词，这里的图层的含义其实是差不多的）上完成的。</p>
</li>
<li><p>Composite：<strong>该区域为合成阶段，浏览器将多个图层按照正确顺序绘制到屏幕上。</strong></p>
</li>
</ul>
<p>假设我们修改了一个几何属性（例如宽度、高度等影响布局的属性），这时Layout阶段受到了影响，浏览器必须检查所有其他区域的元素，然后自动重排页面，任何受到影响的部分都需要重新绘制，并且最终绘制的元素还需要重新进行合成（简单地说就是整个像素管道都要重新执行一遍）。</p>
<p>如果我们只修改了不会影响页面布局的属性，例如背景图片、文字颜色等，那么浏览器会跳过布局阶段，但仍需要重新绘制。</p>
<p><img src="http://wx2.sinaimg.cn/large/63503acbly1fk9uu6h7edj20nr03wq46.jpg"></p>
<p>又或者，我们只修改了一个不影响布局也不影响绘制的属性，那么浏览器将跳过布局与绘制阶段，显然这种改动是性能开销最小的。</p>
<p><img src="http://wx4.sinaimg.cn/large/63503acbly1fk9uu6u2u3j20o5040wfm.jpg"></p>
<p>如果想要知道每个<code>CSS</code>属性将会对哪个阶段产生怎样的影响，请去<a target="_blank" rel="noopener" href="https://csstriggers.com/">CSS Triggers</a>，该网站详细地说明了每个<code>CSS</code>属性会影响到哪个阶段。</p>
<h3 id="使用RequestAnimationFrame函数实现动画"><a href="#使用RequestAnimationFrame函数实现动画" class="headerlink" title="使用RequestAnimationFrame函数实现动画"></a>使用RequestAnimationFrame函数实现动画</h3><hr>
<p>我们经常使用<code>JavaScript</code>来实现动画效果，然而时机不当或长时间运行的<code>JavaScript</code>可能就是导致你性能下降的原因。</p>
<p>避免使用<code>setTimeout()</code>或者<code>setInterval()</code>函数来实现动画效果，这种做法的主要问题是<strong>回调将会在帧中的某个时间点运行，这可能会刚好在末尾（会丢失帧导致发生卡顿）。</strong></p>
<p><img src="http://wx4.sinaimg.cn/large/63503acbly1fka0wxn8m2j20p40by40d.jpg"></p>
<p>有些第三方库仍在使用<code>setTimeout()&amp;setInterval()</code>函数来实现动画效果，这会产生很多不必要的性能下降，例如老版本的<code>JQuery</code>，如果你使用的是<code>JQuery3</code>，那么不必为此担心，<code>JQuery3</code>已经全面改写了动画模块，采用了<code>requestAnimationFrame()</code>函数来实现动画效果。但如果你使用的是之前版本的<code>JQuery</code>，那么就需要<a target="_blank" rel="noopener" href="https://github.com/gnarf/jquery-requestAnimationFrame">jquery-requestAnimationFrame</a>来将<code>setTimeout()</code>替换为<code>requestAnimationFrame()</code>函数。</p>
<p>读到这里，想必一定会对<code>requestAnimationFrame()</code>产生好奇。要想得到一个流畅的动画，我们希望让视觉变化发生在每一帧的开头，而保证<code>JavaScript</code>在帧开始时运行的方式则是使用<code>requestAnimationFrame()</code>函数，本质上它与<code>setTimeout()</code>没有什么区别，都是在递归调用同一个回调函数来不断更新画面以达到动画的效果，<code>requestAnimationFrame()</code>的使用方法如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">updateScreen</span>(<span class="params">time</span>) </span>&#123;</span><br><span class="line">	<span class="comment">// 这是你的动画效果函数</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将你的动画效果函数放入requestAnimationFrame()作为回调函数</span></span><br><span class="line">requestAnimationFrame(updateScreen);</span><br></pre></td></tr></table></figure>
<p>并不是所有浏览器都支持<code>requestAnimationFrame()</code>函数，如<code>IE9</code>（又是万恶的<code>IE</code>），但基本上现代浏览器都会支持这个功能的，如果你需要兼容老旧版本的浏览器，可以使用以下函数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 本段代码截取自Paul Irish : https://gist.github.com/paulirish/1579671</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> lastTime = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">var</span> vendors = [<span class="string">&#x27;ms&#x27;</span>, <span class="string">&#x27;moz&#x27;</span>, <span class="string">&#x27;webkit&#x27;</span>, <span class="string">&#x27;o&#x27;</span>];</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">var</span> x = <span class="number">0</span>; x &lt; vendors.length &amp;&amp; !<span class="built_in">window</span>.requestAnimationFrame; ++x) &#123;</span><br><span class="line">        <span class="built_in">window</span>.requestAnimationFrame = <span class="built_in">window</span>[vendors[x]+<span class="string">&#x27;RequestAnimationFrame&#x27;</span>];</span><br><span class="line">        <span class="built_in">window</span>.cancelAnimationFrame = <span class="built_in">window</span>[vendors[x]+<span class="string">&#x27;CancelAnimationFrame&#x27;</span>] </span><br><span class="line">                                   || <span class="built_in">window</span>[vendors[x]+<span class="string">&#x27;CancelRequestAnimationFrame&#x27;</span>];</span><br><span class="line">    &#125;</span><br><span class="line"> 	</span><br><span class="line">	<span class="comment">// 如果浏览器不支持，则使用setTimeout()</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">window</span>.requestAnimationFrame)</span><br><span class="line">        <span class="built_in">window</span>.requestAnimationFrame = <span class="function"><span class="keyword">function</span>(<span class="params">callback, element</span>) </span>&#123;</span><br><span class="line">            <span class="keyword">var</span> currTime = <span class="keyword">new</span> <span class="built_in">Date</span>().getTime();</span><br><span class="line">            <span class="keyword">var</span> timeToCall = <span class="built_in">Math</span>.max(<span class="number">0</span>, <span class="number">16</span> - (currTime - lastTime));</span><br><span class="line">            <span class="keyword">var</span> id = <span class="built_in">window</span>.setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123; callback(currTime + timeToCall); &#125;, </span><br><span class="line">              timeToCall);</span><br><span class="line">            lastTime = currTime + timeToCall;</span><br><span class="line">            <span class="keyword">return</span> id;</span><br><span class="line">        &#125;;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">window</span>.cancelAnimationFrame)</span><br><span class="line">        <span class="built_in">window</span>.cancelAnimationFrame = <span class="function"><span class="keyword">function</span>(<span class="params">id</span>) </span>&#123;</span><br><span class="line">            <span class="built_in">clearTimeout</span>(id);</span><br><span class="line">        &#125;;</span><br><span class="line">&#125;());</span><br></pre></td></tr></table></figure>
<h3 id="Web-Workers"><a href="#Web-Workers" class="headerlink" title="Web Workers"></a>Web Workers</h3><hr>
<p><strong>我们知道<code>JavaScript</code>是单线程的，但浏览器可不是单线程的</strong>。**<code>JavaScript</code>在浏览器的主线程上运行<strong>，这恰好与样式计算、布局等许多其他情况下的渲染操作一起运行，</strong>如果<code>JavaScript</code>的运行时间过长，就会阻塞这些后续工作，导致帧丢失。**</p>
<p>使用<code>Chrome</code>开发者工具的<code>Timeline</code>功能可以帮助我们查看每个<code>JavaScript</code>脚本的运行时间（包括子脚本），帮助我们发现并突破性能瓶颈。</p>
<p><img src="http://wx3.sinaimg.cn/large/63503acbly1fka16g0tbyj20tq0lqacb.jpg" alt="数据采自掘金"></p>
<p>在找到影响性能的<code>JavaScript</code>脚本后，我们可以通过<code>Web Workers</code>进行优化。<code>Web Workers</code>是<code>HTML5</code>提出的一个标准，它<strong>可以让<code>JavaScript</code>脚本运行在后台线程（类似于创建一个子线程），而后台线程不会影响到主线程中的页面</strong>。不过，<strong>使用<code>Web Workers</code>创建的线程是不能操作<code>DOM</code>树的</strong>（这也是<code>Web Workers</code>没有颠覆<code>JavaScript</code>是单线程的原因，<code>JavaScript</code>之所以一直是单线程设计主要也是因为为了避免多个脚本操作<code>DOM</code>树的同步问题，这会提高很多复杂性），所以它只适合于做一些纯计算的工作（数据的排序、遍历等）。</p>
<p>如果你的<code>JavaScript</code>必须要在主线程中执行，那么只能选择另一种方法。将一个大任务分割为多个小任务（每个占用时间不超过几毫秒），并且在每帧的<code>requestAnimationFrame()</code>函数中运行：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> taskList = breakBigTaskIntoMicroTasks(monsterTaskList);</span><br><span class="line">requestAnimationFrame(processTaskList);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processTaskList</span>(<span class="params">taskStartTime</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> taskFinishTime;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="comment">// 从列表中弹出任务</span></span><br><span class="line">    <span class="keyword">var</span> nextTask = taskList.pop();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行任务</span></span><br><span class="line">    processTask(nextTask);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果有足够的时间进行下一个任务则继续执行</span></span><br><span class="line">    taskFinishTime = <span class="built_in">window</span>.performance.now();</span><br><span class="line">  &#125; <span class="keyword">while</span> (taskFinishTime - taskStartTime &lt; <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (taskList.length &gt; <span class="number">0</span>)</span><br><span class="line">    requestAnimationFrame(processTaskList);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>创建一个<code>Web Workers</code>对象很简单，只需要调用<code>Worker()</code>构造器，然后传入指定脚本的<code>URI</code>。现代主流浏览器均支持<code>Web Workers</code>，除了<code>Internet Explorer</code>（又是万恶的IE），所以我们在下面的示例代码中还需要检测浏览器是否兼容。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myWorker;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span>(Worker) !== <span class="string">&quot;undefined&quot;</span>) &#123;</span><br><span class="line">	<span class="comment">// 支持Web Workers</span></span><br><span class="line">	myWorker = <span class="keyword">new</span> Worker(<span class="string">&quot;worker.js&quot;</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">	<span class="comment">// 不支持Web Workers</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong><code>Web Workers</code>与主线程之间通过<code>postMessage()</code>函数来发送信息，使用<code>onmessage()</code>事件处理函数来响应消息（主线程与子线程之间并没有共享数据，只是通过复制数据来交互）。</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">main.js: </span><br><span class="line"><span class="comment">// 在主线程js中发送数据到myWorker绑定的js脚本线程</span></span><br><span class="line">myWorker.postMessage(<span class="string">&quot;Hello,World&quot;</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">&#x27;Message posted to worker&#x27;</span>);</span><br><span class="line"> </span><br><span class="line">worker.js:</span><br><span class="line"><span class="comment">// onmessage处理函数允许我们在任何时刻，</span></span><br><span class="line"><span class="comment">// 一旦接收到消息就可以执行一些代码，代码中消息本身作为事件的data属性进行使用。</span></span><br><span class="line">onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&quot;Message received from main script.&quot;</span>);</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">&quot;Posting message back to main script.&quot;</span>);</span><br><span class="line">	postMessage(<span class="string">&quot;Hello~&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">main.js:</span><br><span class="line"><span class="comment">// 主线程使用onmessage接收消息</span></span><br><span class="line">myWorker.onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&quot;Received message: &quot;</span> + data);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果你需要从主线程中立刻终止一个运行中的worker，可以调用worker的<code>terminate()</code>函数：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">myWorker.terminate();</span><br></pre></td></tr></table></figure>
<p>myWorker会被立即杀死，不会有任何机会让它继续完成剩下的工作。而在worker线程中也可以调用<code>close()</code>函数进行关闭：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">close();</span><br></pre></td></tr></table></figure>
<p>有关更多的<code>Web Workers</code>使用方法，请参考<a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Using Web Workers - Web APIs | MDN</a>。</p>
<h3 id="降低样式计算的复杂度"><a href="#降低样式计算的复杂度" class="headerlink" title="降低样式计算的复杂度"></a>降低样式计算的复杂度</h3><hr>
<p><strong>每次修改<code>DOM</code>和<code>CSS</code>都会导致浏览器重新计算样式</strong>，在很多情况下还会对页面或页面的一部分重新进行布局计算。</p>
<p>计算样式的第一部分是创建一组匹配选择器（用于计算哪些节点应用哪些样式），第二部分涉及从匹配选择器中获取所有样式规则，并计算出节点的最终样式。</p>
<p><strong>通过降低选择器的复杂性可以提升样式计算的速度。</strong></p>
<p>下面是一个复杂的<code>CSS</code>选择器：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span><span class="selector-pseudo">:nth-last-child(-n+1)</span> <span class="selector-class">.title</span> &#123;</span><br><span class="line">  <span class="comment">/* styles */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>浏览器如果想要找到应用该样式的节点，需要先找到有<code>.title</code>类的节点，然后其父节点正好是负n个子元素+1个带<code>.box</code>类的节点。浏览器计算此结果可能需要大量的时间，但我们可以把选择器的预期行为更改为一个类：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.final-box-title</span> &#123;</span><br><span class="line">  <span class="comment">/* styles */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们只是将<code>CSS</code>的命名模块化（降低选择器的复杂性），然后只让浏览器简单地将选择器与节点进行匹配，这样浏览器计算样式的效率会提升许多。</p>
<p><code>BEM</code>是一种模块化的<code>CSS</code>命名规范，使用这种方法组织<code>CSS</code>不仅结构上十分清晰，也对浏览器的样式查找提供了帮助。</p>
<p><code>BEM</code>其实就是<code>Block,Element,Modifier</code>，它是一种基于组件的开发方式，其背后的思想就是将用户界面划分为独立的块。这样即使是使用复杂的<code>UI</code>也可以轻松快速地开发，并且模块化的方式可以提高代码的复用性。</p>
<p><strong><code>Block</code>是一个功能独立的页面组件（可以被重用），<code>Block</code>的命名方式就像写<code>Class</code>名一样</strong>。如下面的<code>.button</code>就是代表<code>&lt;button&gt;</code>的<code>Block</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">.button &#123;</span><br><span class="line">    background-color: red;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&lt;button class&#x3D;&quot;button&quot;&gt;I&#39;m a button&lt;&#x2F;button&gt;</span><br></pre></td></tr></table></figure>
<p><strong><code>Element</code>是一个不能单独使用的<code>Block</code>的复合部分</strong>。可以认为<code>Element</code>是<code>Block</code>的子节点。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- &#96;search-form&#96;是一个block --&gt;</span><br><span class="line">&lt;form class&#x3D;&quot;search-form&quot;&gt;</span><br><span class="line">    &lt;!-- &#39;search-form__input&#39;是&#39;search-form&#39; block中的一个element --&gt;</span><br><span class="line">    &lt;input class&#x3D;&quot;search-form__input&quot;&gt;</span><br><span class="line"></span><br><span class="line">    &lt;!-- &#39;search-form__button&#39;是&#39;search-form&#39; block中的一个element  --&gt;</span><br><span class="line">    &lt;button class&#x3D;&quot;search-form__button&quot;&gt;Search&lt;&#x2F;button&gt;</span><br><span class="line">&lt;&#x2F;form&gt;</span><br></pre></td></tr></table></figure>
<p><strong><code>Modifier</code>是用于定义<code>Block</code>或<code>Element</code>的外观、状态或行为的实体</strong>。假设，我们有了一个新的需求，对<code>button</code>的背景颜色使用绿色，那么我们可以使用<code>Modifier</code>对<code>.button</code>进行一次扩展：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.button &#123;</span><br><span class="line">    background-color: red;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">.button--secondary &#123;</span><br><span class="line">    background-color: green;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>第一次接触<code>BEM</code>的童鞋可能会对这种命名方式感到奇怪，但<code>BEM</code>重要的是模块化与可维护性的思想，至于命名完全可以按照你所能接受的方式修改。限于篇幅，本文就不再继续探讨<code>BEM</code>了，感兴趣的童鞋可以去看<a target="_blank" rel="noopener" href="https://en.bem.info/methodology/quick-start/">BEM的官方文档</a>。</p>
<h3 id="避免强制同步布局和布局抖动"><a href="#避免强制同步布局和布局抖动" class="headerlink" title="避免强制同步布局和布局抖动"></a>避免强制同步布局和布局抖动</h3><hr>
<p><strong>浏览器每次进行布局计算时几乎总是会作用到整个<code>DOM</code>，如果有大量元素，那么将会需要很长时间才能计算出所有元素的位置与尺寸。</strong></p>
<p>所以我们<strong>应当尽量避免在运行时动态地修改几何属性</strong>（宽度、高度等），因为这些改动都会导致浏览器重新进行布局计算。如果无法避免，那么要**优先使用<code>Flexbox</code>**，它会尽量减少布局所需的开销。</p>
<p><strong>强制同步布局就是使用<code>JavaScript</code>强制浏览器提前执行布局</strong>。需要先明白一点，<strong>在<code>JavaScript</code>运行时，来自上一帧的所有旧布局值都是已知的。</strong></p>
<p>以下代码为例，它在每一帧的开头输出了元素的高度：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">requestAnimationFrame(logBoxHeight);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">logBoxHeight</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(box.offsetHeight);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>但如果在请求高度之前，修改了其样式，就会出现问题，浏览器必须先应用样式，然后进行布局计算，之后才能返回正确的高度。这是不必要的且会产生非常大的开销。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">logBoxHeight</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  box.classList.add(<span class="string">&#x27;super-big&#x27;</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="built_in">console</span>.log(box.offsetHeight);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>正确的做法，应该利用浏览器可以使用上一帧布局值的特性，然后再执行任何写操作：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">logBoxHeight</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(box.offsetHeight);</span><br><span class="line"></span><br><span class="line">  box.classList.add(<span class="string">&#x27;super-big&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>如果接二连三地发生强制同步布局，那么就会产生布局抖动</strong>。以下代码循环处理一组段落，并设置每个段落的宽度以匹配一个名为“box”的元素的宽度。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">resizeAllParagraphsToMatchBlockWidth</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; paragraphs.length; i++) &#123;</span><br><span class="line">    paragraphs[i].style.width = box.offsetWidth + <span class="string">&#x27;px&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这段代码的问题在于每次迭代都会读取<code>box.offsetWidth</code>，然后立即使用此值来更新段落的宽度。在循环的下次迭代中，浏览器必须考虑样式更新这一事实（<code>box.offsetWidth</code>是在上一次迭代中请求的），因此它必须应用样式更改，然后执行布局。这会导致每次迭代都会产生强制同步布局，正确的做法应该先读取值，然后再写入值。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Read.</span></span><br><span class="line"><span class="keyword">var</span> width = box.offsetWidth;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">resizeAllParagraphsToMatchBlockWidth</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; paragraphs.length; i++) &#123;</span><br><span class="line">    <span class="comment">// Now write.</span></span><br><span class="line">    paragraphs[i].style.width = width + <span class="string">&#x27;px&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>要想轻松地解决这个问题，可以使用<a target="_blank" rel="noopener" href="https://github.com/wilsonpage/fastdom">FastDOM</a>进行批量读取与写入，它可以防止强制布局同步与布局抖动。</p>
<h3 id="使用不会触发布局与绘制的属性来实现动画"><a href="#使用不会触发布局与绘制的属性来实现动画" class="headerlink" title="使用不会触发布局与绘制的属性来实现动画"></a>使用不会触发布局与绘制的属性来实现动画</h3><hr>
<p>在像素管道一节中，我们发现有种属性修改后会跳过布局与绘制阶段，这显然会减少不少性能开销。目前只有两种属性符合这个条件：<code>transform</code>和<code>opacity</code> 。</p>
<p>需要注意的是，使用<code>transform</code>和<code>opacity</code>时，更改这些属性所在的元素应处于其自身的图层，<strong>所以我们需要将设置动画的元素单独新建一个图层（这样做的好处是该图层上的重绘可以在不影响其他图层上元素的情况下进行处理。如果你用过<code>Photoshop</code>，想必能够理解多图层工作的方便之处）。</strong></p>
<p>创建新图层的最佳方式是使用<code>will-change</code>属性，<strong>该属性告知浏览器该元素会有哪些变化，这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。</strong></p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.moving-element</span> &#123;</span><br><span class="line">  <span class="attribute">will-change</span>: transform;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 对于不支持 <span class="selector-tag">will-change</span> 但受益于层创建的浏览器，需要使用（滥用）3<span class="selector-tag">D</span> 变形来强制创建一个新层</span><br><span class="line"><span class="selector-class">.moving-element</span> &#123;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">translateZ</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>但不要认为<code>will-change</code>可以提高性能就随便滥用，使用<code>will-change</code>进行预优化与创建图层都需要额外的内存和管理开销，随便滥用只会得不偿失。</strong></p>
<h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><hr>
<ul>
<li><p><a target="_blank" rel="noopener" href="https://developers.google.com/web/">Web   |  Google Developers</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Using Web Workers - Web APIs | MDN</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/CSS/will-change">will-change - CSS | MDN</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://en.bem.info/methodology/quick-start/">Quick start / Methodology / BEM</a></p>
</li>
</ul>

    </div>

    
    
    

    <footer class="post-footer">
          <div class="post-tags">
              <a href="/yuwanzi.io/tags/2017/" rel="tag"># 2017</a>
              <a href="/yuwanzi.io/tags/%E5%89%8D%E7%AB%AF/" rel="tag"># 前端</a>
              <a href="/yuwanzi.io/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/" rel="tag"># 浏览器</a>
          </div>

        

          <div class="post-nav">
            <div class="post-nav-item">
                <a href="/yuwanzi.io/2017/10/03/2017-10-03-BrowserCriticalRenderingPath/" rel="prev" title="浏览器渲染过程与性能优化">
                  <i class="fa fa-chevron-left"></i> 浏览器渲染过程与性能优化
                </a>
            </div>
            <div class="post-nav-item">
                <a href="/yuwanzi.io/2017/10/15/2017-10-15-JavaAnnotation/" rel="next" title="注解的那点事儿">
                  注解的那点事儿 <i class="fa fa-chevron-right"></i>
                </a>
            </div>
          </div>
    </footer>
  </article>
</div>







<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      const commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>
</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">玉丸子</span>
</div>
  <div class="powered-by">Erstellt mit  <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" class="theme-link" rel="noopener" target="_blank">NexT.Muse</a>
  </div>

    </div>
  </footer>

  
  <script src="//cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
<script src="/yuwanzi.io/js/utils.js"></script><script src="/yuwanzi.io/js/motion.js"></script><script src="/yuwanzi.io/js/schemes/muse.js"></script><script src="/yuwanzi.io/js/next-boot.js"></script>

  






  





</body>
</html>
